﻿using System;
using System.Linq;
using System.Reflection;
using Microsoft.Scripting.Runtime;

namespace Cjc.Calculation.ExpressionEngine.Excel.Runtime
{
	/// <summary>
	/// Excel global function implementations
	/// </summary>
	/// <remarks>Include an optional WorksheetScope as the first parameter if required.</remarks>
	public class ExcelHelpers
	{
		public static double Exp( double d ) { return Math.Exp( d ); }
		public static decimal Abs( decimal d ) { return Math.Abs( d ); }
		public static double Pow( decimal x, decimal y ) { return Math.Pow( (double)x, (double)y ); }
		public static DateTime Today() { return DateTime.Today; }

		public static decimal Min( params decimal[] p ) { return p.Min(); }
		public static decimal Max( params decimal[] p ) { return p.Max(); }
		public static decimal Round( decimal x, int decimals ) { return Math.Round( x, decimals ); }

		public static double RoundDown( decimal number, int digits )
		{
			var x = Math.Pow( 10, digits ); // 1, 10, 100, 1000, 10000
			var y = (double)number * x;
			return Math.Round( Math.Floor( y ) / x, digits < 0 ? 0 : digits );
		}

		public static double RoundUp( decimal number, int digits )
		{
			var x = Math.Pow( 10, digits ); // 1, 10, 100, 1000, 10000
			var y = (double)number * x;
			bool isExact = Math.Floor( y ) == y;
			return Math.Round( Math.Floor( y + ( isExact ? 0 : 1 ) ) / x, digits < 0 ? 0 : digits );
		}

		public static bool And( params bool[] conditions ) { return conditions.All( i => i ); }
		public static bool Or( params bool[] conditions ) { return conditions.Any( i => i ); }

		public static string Left( string text, int count ) { return !string.IsNullOrEmpty( text ) ? text.Substring( 0, count ) : ""; }
		public static string Right( string text, int count ) { return !string.IsNullOrEmpty( text ) ? text.Substring( text.Length - count ) : ""; }

		public static string Mid( string text, int start, int characters )
		{
			if ( string.IsNullOrEmpty( text ) ) return "";

			var length = text.Length;
			var startPos = start < 0 ? 0 : start - 1; //Javascript is 0 based index, excel is 1 based.
			var returnLength = length - characters;

			return ( characters <= 0 || startPos > length || returnLength < 0 ) // Invalid bound, return blank string
				? ""
				: text.Substring( startPos, characters );
		}

		public static object Sum( params decimal[] values )
		{
			return values.Sum();
		}

		public static object Search( string find, string withIn ) { return Search( find, withIn, 1 ); }

		public static object Search( string find, string withIn, int start )
		{
			if ( string.IsNullOrEmpty( find ) ) return null;

			string search = withIn.Replace( "~*", "*" ).Replace( "~?", "?" );

			var pos = search.IndexOf( find, start - 1, StringComparison.InvariantCultureIgnoreCase );

			return pos > -1 ? (int?)( pos + 1 ) : null;
		}

        public static string Dump(WorksheetScope scope)
		{
			return Dump( scope, false );
		}

        // We're going to get the DebugView property on Expression using reflection because it is marked as private (and so may change!)
        private static PropertyInfo debugView = typeof(System.Linq.Expressions.Expression).GetProperty("DebugView", BindingFlags.NonPublic | BindingFlags.Instance);

        public static string Dump(WorksheetScope scope, bool verbose)
		{
			var result = new System.Text.StringBuilder();

			foreach ( var cell in scope.Worksheet.Cells )
			{
				if ( result.Length > 0 ) result.AppendLine();

				var expression = verbose
                    ? debugView.GetValue(cell.Value.Formula.Expression, new object[] { }).ToString()
                    : cell.Value.Formula.ExpressionText;

				result.AppendFormat(
					"{0} = {1}{2}",
					cell.Key,
					cell.Value.Value,
					expression != null ? " [ = " + expression + " ]" : null );
			}

			return result.ToString();
		}
	}
}